/*
 *   This File is Part of CuteOgre Game Engine
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *	Copyright 2010 Alexandre Pires ( c.alexandre.pires@gmail.com )
 *  Created on: 2010/08/22
 */

#include "Ogg/TheoraStream.h"
#include "Ogg/OggFile.h"

#include "assert.h"

#define TH_MAX( a, b ) ((a > b) ? a : b)
#define TH_MIN( a, b ) ((a < b) ? a : b)
#define CLIP_RGB_COLOR( rgb_color_test ) TH_MAX( TH_MIN(rgb_color_test, 255), 0 )

unsigned int YTable[256];
unsigned int BUTable[256];
unsigned int GUTable[256];
unsigned int GVTable[256];
unsigned int RVTable[256];

TheoraStream::TheoraStream(int rSerial, OggFile* rFile) :
	mSetup(0), mCtx(0) {
	mSerial = rSerial;
	mFile = rFile;
	mHeaderDone = false;
	mActive = true;
	mGranulepos = 0;
	ogg_stream_init(&mState, rSerial);
}

TheoraStream::~TheoraStream() {
	ogg_stream_clear(&mState);
	th_setup_free(mSetup);
	th_decode_free(mCtx);
	mFile = 0;
}

bool TheoraStream::handle_theora_header(ogg_packet* rPacket) {

	int ret = th_decode_headerin(&mInfo, &mComment, &mSetup, rPacket);

	if (ret == TH_ENOTFORMAT)
		return false; // Not a theora header

	if (ret > 0) {
		return false;
	}

	// Any other return value is treated as a fatal error
	assert(ret == 0);

	// This is not a header packet. It is the first
	// video data packet.
	mHeaderDone = true;
	mActive = true;
	return true;
}

void TheoraStream::processHeaders() {

	ogg_packet packet;

	int ret = 0;
	bool done = false;

	th_info_init(&mInfo);
	th_comment_init(&mComment);

	while (!done && mFile->peekPacket(mSerial, &packet)) {

		done = handle_theora_header(&packet);

		if (!done) {
			// Consume the packet
			mFile->getPacket(mSerial, &packet);
		}
	}

	mCtx = th_decode_alloc(&mInfo, mSetup);
	assert(mCtx != NULL);

	int ppmax = 0;
	ret = th_decode_ctl(mCtx, TH_DECCTL_GET_PPLEVEL_MAX, &ppmax, sizeof(ppmax));
	assert(ret == 0);

	ppmax = 0;
	ret = th_decode_ctl(mCtx, TH_DECCTL_SET_PPLEVEL, &ppmax, sizeof(ppmax));
	assert(ret == 0);
}

bool TheoraStream::read(Ogre::PixelBox* rBuffer) {

	if (!mHeaderDone || !mActive)
		return false;

	ogg_packet packet;

	if (mFile->getPacket(mSerial, &packet)) {

		int ret = th_decode_packetin(mCtx, &packet, &mGranulepos);

		if (ret == 0) {

			// We have a frame. Get the YUV data

			th_ycbcr_buffer yuvData;
			ret = th_decode_ycbcr_out(mCtx, yuvData);

			unsigned char* ySrc = yuvData[0].data;
			unsigned char* ySrc2 = yuvData[0].data;
			unsigned char* ySrcEnd;
			unsigned char* uSrc = yuvData[1].data;
			unsigned char* uSrc2 = yuvData[1].data;
			unsigned char* vSrc = yuvData[2].data;
			unsigned char* vSrc2 = yuvData[2].data;

			int t, bU, gUV, rV, rgbY;

			Ogre::uint8 r = 255;
			Ogre::uint8 g = 0;
			Ogre::uint8 b = 0;

			Ogre::uint32* out = static_cast<Ogre::uint32*> (rBuffer->data);

			for (int y = 0; y < yuvData[0].height; y++) {

				t = 0;

				ySrc = ySrc2;
				ySrcEnd = ySrc2 + yuvData[0].width;

				uSrc = uSrc2;
				vSrc = vSrc2;

				while (ySrc != ySrcEnd) {

					rgbY = YTable[*ySrc];

					t = !t;

					if (t == 1) {
						rV = RVTable[*vSrc];
						gUV = GUTable[*uSrc] + GVTable[*vSrc];
						bU = BUTable[*uSrc];
						uSrc++;
						vSrc++;
					}

					r = CLIP_RGB_COLOR((rgbY + rV) >> 13);
					g = CLIP_RGB_COLOR((rgbY - gUV) >> 13);
					b = CLIP_RGB_COLOR((rgbY + bU) >> 13);

					Ogre::PixelUtil::packColour(r, g, b, 0, Ogre::PF_X8R8G8B8,
							out);
					out++;
					ySrc++;
				}

				ySrc2 += yuvData[0].stride;

				if (y & 1) {
					uSrc2 += yuvData[1].stride;
					vSrc2 += yuvData[2].stride;
				}
			}
		}

		mLastFrameTime = th_granule_time(mCtx, mGranulepos);

		return true;
	}

	return false;
}

bool TheoraStream::checkHeaderSignature(ogg_packet* rPacket) {

	char* sigblock = (char*) (rPacket->packet);
	unsigned char type = *sigblock++;

	QString signature = QString().fromAscii(sigblock, 6);

	if ((type == 0x80 || type == 0x81 || type == 0x82) && signature.contains(
			"theora")) {

		// THEORA HEADER ( From theora format spec. )
		//	 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
		//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		//	|      0x8x     |      ‘t’      |      ‘h’      |      ‘e’       |
		//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		//	|      ‘o’      |      ‘r’      |      ‘a’
		// The identification header is type 0x80

		return true;
	}

	return false;
}

bool TheoraStream::checkHeaderSignature(ogg_page* rPage) {

	char* sigblock = (char*) (rPage->body);
	unsigned char type = *sigblock++;

	QString signature = QString().fromAscii(sigblock, 6);

	if ((type == 0x80 || type == 0x81 || type == 0x82) && signature.contains(
			"theora")) {

		// THEORA HEADER ( From theora format spec. )
		//	 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
		//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		//	|      0x8x     |      ‘t’      |      ‘h’      |      ‘e’       |
		//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		//	|      ‘o’      |      ‘r’      |      ‘a’
		// The identification header is type 0x80

		return true;
	}

	return false;
}

void createYUVtoRGBtables() {

	double scale = 1L << 13, temp;

	for (int i = 0; i < 256; i++) {
		temp = i - 128;

		YTable[i] = (unsigned int) ((1.164 * scale + 0.5) * (i - 16)); //Calc Y component

		RVTable[i] = (unsigned int) ((1.596 * scale + 0.5) * temp); //Calc R component

		GUTable[i] = (unsigned int) ((0.391 * scale + 0.5) * temp); //Calc G u & v components
		GVTable[i] = (unsigned int) ((0.813 * scale + 0.5) * temp);

		BUTable[i] = (unsigned int) ((2.018 * scale + 0.5) * temp); //Calc B component
	}
}
